﻿using System;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace HB.WpfEx
{
    public class ImagePoint
    {
        public double X { get; set; }
        public double Y { get; set; }
        public int Index { get; set; }
    }
    /// <summary>
    /// 使用grid作为容器
    /// <Grid>
    ///    <Image name='img'></Image>
    /// <Grid>
    /// </summary>
    public class ImageScale
    {
        public event Action<Int32Rect> ViewChanged;
        public event Action<ImagePoint> MouseMove;
        public event Action<ImagePoint> LeftMouseDown;
        public event Action<ImagePoint> RightMouseDown;
        TransformGroup TranGroup;
        ScaleTransform st = null;
        public TranslateTransform tt = null;
        FrameworkElement box = null;
        FrameworkElement boxp = null;
        Image pic;
        Point PrePoint { get; set; } = new Point(-1, -1);
        Point PointZero { get; set; } = new Point(-1, -1);
        double PreMoveTransformPointX { get; set; }
        double PreMoveTransformPointY { get; set; }

        public void Dispose()
        {
            pic.MouseDown -= Pic_MouseDown;
            pic.MouseMove -= Pic_MouseMove;
            pic.MouseWheel -= Pic_MouseWheel;
        }
        /// <summary>
        /// 显示当前矩阵界面
        /// </summary>
        /// <param name="rect"></param>
        /// <exception cref="Exception"></exception>
        public void ShowView(Int32Rect rect)
        {

            if (pic.Source == null) { return; }

            Size imgSize = new Size(pic.Source.Width, pic.Source.Height);
            if (rect.X > imgSize.Width || rect.Y > imgSize.Height)
            {
                throw new Exception("X Or Y Is Out Of Range ");
            }

            double scalew = boxp.ActualWidth / rect.Width;
            double scaleh = boxp.ActualHeight / rect.Height;
            double scale = Math.Min(scaleh, scalew);
            pic.Dispatcher.BeginInvoke(new Action(async () =>
            {
                st.ScaleX = scale;
                st.ScaleY = scale;
                await Task.Delay(100);//等待UI渲染
                //视野中心离目标的距离
                var interval = boxp.TranslatePoint(new Point(boxp.ActualWidth / 2.0, boxp.ActualHeight / 2.0), pic);
                //放大后计算距离，偏移到设置的中心点
                tt.X += (interval.X - rect.X - rect.Width / 2.0) * scale;
                tt.Y += (interval.Y - rect.Y - rect.Height / 2.0) * scale;


            })).Wait();

        }
        /// <summary>
        /// 获取当前视野大小
        /// </summary>
        /// <returns></returns>
        public Int32Rect GetViewRect()
        {
            if (pic.Source == null) { return new Int32Rect(0, 0, 0, 0); }

            var pstart = boxp.TranslatePoint(new Point(0, 0), pic);
            var pend = boxp.TranslatePoint(new Point(boxp.ActualWidth, boxp.ActualHeight), pic);

            int x = Math.Max(0, (int)pstart.X);
            int y = Math.Max(0, (int)pstart.Y);
            int width = Math.Min((int)pend.X, (int)Math.Round(pic.Source.Width)) - x;
            int height = Math.Min((int)Math.Round(pic.Source.Height), (int)pend.Y) - y;
            return new Int32Rect(x, y, width, height);


        }
        /// <summary>
        /// 中心缩放
        /// </summary>
        /// <param name="radio"></param>
        public void Scale(double radio)
        {
            if (pic.Source == null) { return; }
            if (radio < 1) { radio = 1; }
            var startW = pic.Source.Width * (radio - 1) / 2 / radio;
            var startH = pic.Source.Height * (radio - 1) / 2 / radio;
            ShowView(new Int32Rect((int)startW, (int)startH, (int)(pic.Source.Width / radio), (int)(pic.Source.Height / radio)));
        }
        /// <summary>
        /// 使用NearestNeighbor算法进行缩放
        /// </summary>
        /// <param name="pic"></param>
        public ImageScale(Image pic)
        {
            RenderOptions.SetBitmapScalingMode(pic, BitmapScalingMode.NearestNeighbor);
            this.box = pic.Parent as FrameworkElement;
            this.boxp = box.Parent as FrameworkElement;
            this.pic = pic;

            pic.SizeChanged += (a, b) =>
            {
                if (pic.Source == null) { return; }
                ShowView(new Int32Rect(0, 0, (int)pic.Source.Width, (int)pic.Source.Height));
            };
            box.Width = 40960000;
            box.Height = 40960000;
            box.HorizontalAlignment = HorizontalAlignment.Center;
            box.VerticalAlignment = VerticalAlignment.Center;

            pic.HorizontalAlignment = HorizontalAlignment.Center;
            pic.VerticalAlignment = VerticalAlignment.Center;
            pic.Stretch = Stretch.None;
            box.ClipToBounds = true;
            st = new ScaleTransform();
            tt = new TranslateTransform();
            
            TranGroup = new TransformGroup();
            TranGroup.Children.Add(st);
            TranGroup.Children.Add(tt);
         
            pic.RenderTransform = TranGroup;

            pic.MouseDown += Pic_MouseDown;
            pic.MouseMove += Pic_MouseMove;
            pic.MouseWheel += Pic_MouseWheel;

        }

        private void Pic_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            double delta = e.Delta > 0 ? 0.2 : -0.2;
            if (st.ScaleX + delta < 0.1) { return; }
            var p = e.GetPosition(pic);
            //放大后有偏移，需要移动回来
            st.ScaleX += delta;
            st.ScaleY += delta;
            var p2 = e.GetPosition(pic);
            tt.X += (p2.X - p.X) * st.ScaleX;
            tt.Y += (p2.Y - p.Y) * st.ScaleY;
            if (ViewChanged != null)
            {
                var rect = GetViewRect();
                ViewChanged.Invoke(rect);
            }
        }

        private void Pic_MouseMove(object sender, MouseEventArgs e)
        {

            var pb = e.GetPosition(box);
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                if (PrePoint != PointZero)
                {
                    tt.X = PreMoveTransformPointX + (pb.X - PrePoint.X);
                    tt.Y = PreMoveTransformPointY + (pb.Y - PrePoint.Y);
                }
            }
            if (MouseMove != null)
            {
                var pp = e.GetPosition(pic);
                ImagePoint ip = new ImagePoint() { X = pp.X, Y = pp.Y };
                ip.Index = (int)pic.Source.Width * (int)pp.Y + (int)pp.X;
                MouseMove.Invoke(ip);
            }
            if (ViewChanged != null)
            {
                var rect = GetViewRect();
                ViewChanged.Invoke(rect);
            }
        }

        private void Pic_MouseDown(object sender, MouseButtonEventArgs e)
        {
            PrePoint = e.GetPosition(box);
            PreMoveTransformPointX = tt.X;
            PreMoveTransformPointY = tt.Y;
            if (LeftMouseDown != null || RightMouseDown != null)
            {
                var pp = e.GetPosition(pic);
                ImagePoint ip = new ImagePoint() { X = pp.X, Y = pp.Y };
                ip.Index = (int)pic.Source.Width * (int)pp.Y + (int)pp.X;
                if (e.LeftButton == MouseButtonState.Pressed)
                {
                    LeftMouseDown?.Invoke(ip);
                }
                if (e.RightButton == MouseButtonState.Pressed)
                {
                    RightMouseDown?.Invoke(ip);
                }
            }
        }
    }
}
